#include "game.hh"
#include <string>
#include <vector>
#include <cstring>
#include <iostream>

enum class TokenType
{
    NUMBER,
    BLUE, RED, GREEN,
    GAME,
    COMMA, SEMICOLON, COLON,
};

struct Token
{
    Token(TokenType t, int v = -1) :
        type{t}, value{v} {}
    TokenType type;
    int value;
};

Game::Game(const std::string& message) :
    _id{-1},
    _sets{1}
{
    // Parse 'message' as: [Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green]
    // Lexing
    std::vector<Token> tokens{};
    int current{0};

    while (current < message.size())
    {
        const char& ch{ message[current] };

        switch (ch)
        {
        case ',':
            tokens.emplace_back(TokenType::COMMA); 
            current += 1; break;
        case ';':
            tokens.emplace_back(TokenType::SEMICOLON);
            current += 1; break;
        case ':':
            tokens.emplace_back(TokenType::COLON);
            current += 1; break;
        case '0': case '1' : case '2' : case '3' : case '4': 
        case '5': case '6' : case '7' : case '8' : case '9': 
        {
            auto isdigit = [](char c){
                return c >= '0' && c <= '9';
            };
            
            int pos{current};
            while ( isdigit(message[++pos]) ) {}
            
            int value{ std::stoi( message.substr(current, (pos - current))) };
            tokens.emplace_back(TokenType::NUMBER, value);
            
            current = pos;
            break;
        }
        case ' ': case '\n' : case '\t' : case '\r':
            current += 1; break;
        default:
            // Keywords
            if ( std::strncmp(&ch, "Game", 4) == 0)
            {
                tokens.emplace_back(TokenType::GAME);
                current += 4;
                break;
            }
            else if ( std::strncmp(&ch, "red", 3) == 0)
            {
                tokens.emplace_back(TokenType::RED);
                current += 3;
                break;
            }
            else if ( std::strncmp(&ch, "green", 5) == 0)
            {
                tokens.emplace_back(TokenType::GREEN);
                current += 5;
                break;
            }
            else if ( std::strncmp(&ch, "blue", 4) == 0)
            {
                tokens.emplace_back(TokenType::BLUE);
                current += 4;
                break;
            }

            std::cout << "Unkown character '" << ch  << '\'' << std::endl;
            std::exit(-1);
            break;
        }
    }

    // Parsing
    current = 0;
    while (current < tokens.size())
    {
        Token& token{ tokens[current] };
        switch (token.type)
        {
        case TokenType::GAME:
            this->_id = tokens[++current].value;
            current += 1;
            break;
        case TokenType::RED:
            this->_sets.back().red = tokens[--current].value;
            current += 2;
            break;  
        case TokenType::BLUE:
            this->_sets.back().blue = tokens[--current].value;
            current += 2;
            break; 
        case TokenType::GREEN:
            this->_sets.back().green = tokens[--current].value;
            current += 2;
            break;
        case TokenType::SEMICOLON:
            this->_sets.emplace_back(0, 0, 0);
            current += 1;
            break;
        default:
            current += 1;
            break;
        }
    }
}

std::ostream& operator << (std::ostream& out, const Game& game)
{
    out << "Game " << game.id() << ": [";
    for (int i = 0; i < game.sets().size(); ++i)
    {
        if (i != 0)
            out << ", ";
        const CubesSize& size{ game.sets()[i] };
        out << size;
    }
    out << ']';

    return out;
}

std::ostream& operator << (std::ostream& out, const CubesSize& size)
{
    out << "(" << size.red << ", " << size.green << ", " << size.blue << ")";
    return out;
}

CubesSize operator + (const CubesSize& size1, const CubesSize& size2)
{
    return CubesSize{ size1.red + size2.red, 
                      size1.green + size2.green,
                      size1.blue + size2.blue };
}

CubesSize operator - (const CubesSize& size1, const CubesSize& size2)
{
    return CubesSize{ size1.red - size2.red, 
                      size1.green - size2.green,
                      size1.blue - size2.blue };
}

bool operator == (const CubesSize& size1, const CubesSize& size2)
{
    return size1.red == size2.red && 
           size1.green - size2.green &&
           size1.blue - size2.blue;
}